package cbbx_sm.utils;

import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Vector;

import org.jgrapht.WeightedGraph;
import org.jgrapht.graph.DefaultWeightedEdge;
import org.jgrapht.graph.SimpleWeightedGraph;

import cbbx_sm.parser.BoundingBox;
import cbbx_sm.parser.CameraData;
import cbbx_sm.parser.Entity;
import cbbx_sm.parser.Frame;
import cbbx_sm.probabilistic_model.Cluster;
import cbbx_sm.probabilistic_model.Clustering;


/**
 * This class contains some useful methods.
 * 
 * @author Alessio Della Motta - University of California, Irvine
 */
public class Utility {
	
	/*-*******************************************************************/
	public final static double[][] clusters_14_15v2 = 
			{{119.375, 399.125}, 
			{531.2906976744184, 310.0348837209302},
			{439.29910714285717, 233.08482142857142}, 
			{370.44654088050305, 323.04088050314476}};
	
	public final static double[][] clusters_13_15v2 = 
		   {{265.5, 419.0}, 
		   {72.41666666666667, 289.7777777777779},
		   {151.79173989455182, 181.72144112478034}, 
		   {276.9262048192773, 157.38253012048185},
		   {512.4269005847954, 257.233918128655}};
	
	public final static double[][] clusters_15_15v2 = 
		   {{206.1046511627907, 342.20930232558146}, 
		   {384.0666666666666, 280.1499999999999},
		   {58.523731587561436, 204.40752864157108}};
	
	public final static double[][] clusters_16_15v2 = 
		   {{487.625, 334.65865384615387}, 
		   {449.42074198988183, 178.2521079258009},
		   {572.3780864197531, 278.2654320987656}};
	
	public final static double[][] clusters_17_15v2 = 
		   {{464.8952380952381, 338.87142857142857}, 
		   {447.52539404553426, 243.60595446584938},
		   {210.1887755102041, 338.9285714285713}};
	
	public final static double[][] clusters_18_15v1 = 
		   {{449.2857142857143, 388.0}, 
		   {298.7561983471075, 183.88842975206606},
		   {336.3821428571429, 310.6196428571426}};
	
//	public final static double[][] clusters_18_15v2 = 
//		   {{449.2857142857143, 388.0}, 
//		   {325.02867830423975, 272.3790523690771}};
	
	
	public static List<Cluster> getClusters(double[][] clustersArray, String cameraId){
		List<Cluster> clusters = new Vector<Cluster>();
		for (int i = 0; i < clustersArray.length; i++){
			Cluster c = new Cluster(cameraId);
			c.setX(clustersArray[i][0]);
			c.setY(clustersArray[i][1]);
			clusters.add(c);
		}
		return clusters;
	}
	
	/*
	 * New methods for parsing clusters.
	 */
	public static List<Cluster> getClusters(String filename, String cameraId, CameraData data){
		List<Cluster> clusters = null;
		try {
			clusters = Clustering.parseClusters(filename, cameraId);
			for (Cluster cluster : clusters){
				cluster.setCameraId(cameraId);
				Clustering._updateClustersForRadius(clusters, data.getAllBoundingBoxes());
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		return clusters;
	}
	
	public static List<Cluster> getClusters(String filename, String cameraId, double radius){
		List<Cluster> clusters = null;
		try {
			clusters = Clustering.parseClusters(filename, cameraId);
			for (Cluster cluster : clusters){
				cluster.setCameraId(cameraId);
				cluster.setRadius(radius);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		return clusters;
	}
	/*-*********************************************************************/
	
	/**
	 * Give boxes from a BBoxData structure.
	 */
	public static Set<BoundingBox> getBoxes(CameraData data){
		Set<BoundingBox> boxes = new HashSet<BoundingBox>();
		for (Frame f : data.getFrames())
			for (Entity e : f.getEntities())
				boxes.add(e.getBoxes()[ExperimentManager.boxLocationId]);
		return boxes;
	}
	
	/**
	 * Calculate a scoring function of a prediction as the ratio between the
	 * intersection area over the total area.
	 * 
	 * @param box
	 * @param prediction
	 * @param maxW
	 * @param maxH
	 * @return score
	 */
	public static double scoringFunction(BoundingBox box, Cluster prediction, int maxW, int maxH){
		double score;
		
		if (prediction == null){
			double stayOutArea = maxW * maxH;
			double boxArea = box.getArea();
			score = boxArea / stayOutArea;
		} else score = getNumericalIntersection(prediction, box) / (Math.PI * prediction.getRadius() * prediction.getRadius());
	
		if (score > 1.0) score = 1.0;
//		if (score < 0.0) score = 0.0;
		
		return score;
	}
	
	/**
	 * Numerical approximate solution for intersection of a cluster and
	 * a bounding box.
	 * 
	 * @param cluster
	 * @param box
	 * @return the area of intersection.
	 */
	public static int getNumericalIntersection(Cluster cluster, BoundingBox box){
		int intersection = 0;
		for (int x = box.getX_ul(); x < box.getX_br(); x++)
			for (int y = box.getY_ul(); y < box.getY_br(); y++)
				if (cluster.shouldContain(x, y)) intersection++;
		
		return intersection;
	}
	
	/**
	 * Build a weighted  completely connected graph, with each bounding box is a vertex
	 * and edges' weight are bounding boxes center distances.
	 * 
	 * If Nbbox is the total number of bounding boxes, the algorithm will perform
	 * Nbbox *(Nbbox - 1) / 2 iterations.
	 * 
	 * @param bounding boxes
	 * @return Graph of bounding boxes
	 */
	public static WeightedGraph<BoundingBox, DefaultWeightedEdge> buildBBoxesGraph(Collection<BoundingBox> boxes){
		WeightedGraph<BoundingBox, DefaultWeightedEdge> g =
            new SimpleWeightedGraph<BoundingBox, DefaultWeightedEdge>(DefaultWeightedEdge.class);

		for (BoundingBox b : boxes) addAndLinkVertex(g, b);
		
		return g;
	}
	
	/**
	 * Build the graph from BBoxData.
	 * 
	 * @param data
	 * @return
	 */
	public static WeightedGraph<BoundingBox, DefaultWeightedEdge> buildBBoxesGraph(CameraData data){
		Collection<BoundingBox> boxes = new Vector<BoundingBox>();
		
		for (Frame f : data.getFrames())
			for (Entity e : f.getEntities())
				boxes.add(e.getBoxes()[ExperimentManager.boxLocationId]);
		
		return buildBBoxesGraph(boxes);
	}
	
	/**
	 * Insert the new vertex and link it to all other edges in the graph,
	 * assigning at each edges its weight (i.e., bounding box distance).
	 * 
	 * @param g
	 * @param box
	 */
	private static void addAndLinkVertex(WeightedGraph<BoundingBox, DefaultWeightedEdge> g, BoundingBox box){
		// Adding the vertex to the graph
		g.addVertex(box);

		/*
		 * Linking the current vertex with all other vertexes in the graph
		 * and assigning edges' weight 
		 */
		for (BoundingBox b : g.vertexSet()){
			// if current instance is another instance of bounding box...
			if (b != box && g.getEdge(b, box) == null){
				// Compute the distance (edge weight)
				double distance = Utility.computeBBoxCenterDistance(b, box);
				// Adding the edge to the graph
				DefaultWeightedEdge newEdge = g.addEdge(b, box);
				// Setting the edge's weight
				g.setEdgeWeight(newEdge, distance);
			}
		}
	}
	
	
	//***********************************************************************************
	//***********************************************************************************
	//	Pay attention: must be compatible with the distance you use (now euclidean dist.)
	//***********************************************************************************
	//***********************************************************************************
	/**
	 * Compute the distance between the centers of two bounding boxes.
	 * 
	 * @param b1 - bbox n.1
	 * @param b2 - bbox n.2
	 * @return distance
	 */
	public static double computeBBoxCenterDistance(BoundingBox b1, BoundingBox b2){
		return Math.sqrt(
				Math.pow(b1.getCenterX() - b2.getCenterX(), 2)
				+
				Math.pow(b1.getCenterY() - b2.getCenterY(), 2)
			   );
	}
	
	
	/**
	 * Pitagora's theorem for finding the max distance (diagonal)
	 * in an image (frame).
	 * 
	 * @param frameSizeX
	 * @param frameSizeY
	 * @return distance
	 */
	public static double getMaxDistance(int frameSizeX, int frameSizeY){
		return Math.sqrt(
				Math.pow(frameSizeX, 2)
				+
				Math.pow(frameSizeY, 2)
			   );
	}
	//***********************************************************************************
	//***********************************************************************************
	
	/**
	 * Compute the normalized distance in [0, 1] between the centers 
	 * of two bounding boxes.
	 * 
	 * @param b1 - bbox n.1
	 * @param b2 - bbox n.2
	 * @param maxDistance - maximum possible distance between two bboxes.
	 * @return normalized distance
	 */
	public static double computeNormalizedBBoxCenterDistance(BoundingBox b1, BoundingBox b2, double maxDistance){
		return computeBBoxCenterDistance(b1, b2) / maxDistance;
	}
	
	/**
	 * ALFA:
	 * 
	 * This method tries to find a correlation between bounding boxes in different
	 * frames, in order to assert if they represent the same entity.
	 * The function is the following:
	 * 
	 *    correlation = (1 - dnorm ^ 2) / ((t2 - t1) ^ 2 + 1)
	 * 
	 * Probably is not a good function!!! It is only for try some tests...
	 * 
	 * @param b1
	 * @param b2
	 * @param timestamp1
	 * @param timestamp2
	 * @return
	 */
	public static double computeCorrelation(BoundingBox b1, BoundingBox b2, long timestamp1, long timestamp2, double maxDistance){
		double normalizedDistance = computeNormalizedBBoxCenterDistance(b1, b2, maxDistance);
		double den = (double) ((timestamp2 - timestamp1) * (timestamp2 - timestamp1)) + 1.0;
		double correlation = (1.0 - normalizedDistance * normalizedDistance) / den;
		
		return correlation;
	}
}
